﻿/*
The MIT License (MIT)
Copyright (c) 2006 Damien Miller djm@mindrot.org (jBCrypt)
Copyright (c) 2013 Ryan D. Emerle (.Net port)
Copyright (c) 2016/2025 Chris McKee (.Net-core port / patches / new features)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
(the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
*/

using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;

namespace BCryptNet;

/// <summary>
/// Base class of the bcrypt api
/// </summary>
public class BCryptCore
{
    // BCrypt parameters
    /// <summary>
    /// Default Work Factor
    /// </summary>
    internal const int DefaultRounds = 11;

    private const int BCryptSaltLen = 128 / 8; // 128 bits

    internal static readonly Encoding SafeUTF8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

    // Blowfish parameters
    private const int BlowfishNumRounds = 16;

    /// <summary>
    /// RandomNumberGenerator.Create calls RandomNumberGenerator.Create("System.Security.Cryptography.RandomNumberGenerator"), which will create an instance of RNGCryptoServiceProvider.
    /// https://msdn.microsoft.com/en-us/library/42ks8fz1
    /// </summary>
    internal static readonly RandomNumberGenerator RngCsp = RandomNumberGenerator.Create(); // secure PRNG

    #region Initial contents of key schedule

    private static readonly uint[] POrig = new uint[]
    {
        0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, 0x452821e6,
        0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b
    };

    private static readonly uint[] SOrig = new uint[]
    {
        0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947,
        0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658,
        0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, 0xc5d1b023, 0x286085f0, 0xca417918,
        0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
        0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af,
        0x7c72e993, 0xb3ee1411, 0x636fbc2a, 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c,
        0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60,
        0x5dec8032, 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
        0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, 0x6a51a0d2,
        0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176,
        0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, 0xe06f75d8, 0x85c12073, 0x401a449f,
        0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
        0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6,
        0x409f60c4, 0x5e5c9ec2, 0x196a2463, 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c,
        0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39,
        0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
        0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, 0x9e5c57bb,
        0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8,
        0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, 0x9a53e479, 0xb6f84565, 0xd28e49bc,
        0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
        0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb,
        0xf2122b64, 0x8888b812, 0x900df01c, 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777,
        0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81,
        0xd2ada8d9, 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
        0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, 0x2464369b,
        0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9,
        0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, 0xd60f573f, 0xbc9bc6e4, 0x2b60a476,
        0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
        0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0,
        0x49a7df7d, 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29,
        0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6,
        0xf0255dc1, 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
        0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37,
        0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, 0xd19113f9, 0x7ca92ff6,
        0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e,
        0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
        0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f,
        0x2e6b7124, 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa,
        0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002,
        0x133ae4dd, 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
        0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c,
        0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, 0xc6150eba, 0x94e2ea78,
        0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e,
        0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
        0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830,
        0xeb61bd96, 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7,
        0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5,
        0xc021b8f7, 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
        0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb,
        0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, 0x5d4a14d9, 0xe864b7e3,
        0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d,
        0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
        0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a,
        0x0c55f5ea, 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460,
        0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc,
        0x800bcadc, 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
        0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, 0xe93d5a68,
        0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a,
        0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, 0x70f4ddd3,
        0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
        0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2,
        0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9,
        0xee39d7ab, 0x3b124e8b, 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7,
        0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
        0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, 0xfdf8e802,
        0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f,
        0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, 0xdff8e8a3,
        0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
        0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f,
        0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8,
        0x991be14c, 0xdb6e6b0d, 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92,
        0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
        0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, 0x6a124237,
        0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386,
        0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086, 0x60787bf8,
        0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
        0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3,
        0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770,
        0x8cd55591, 0xc902de4c, 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1,
        0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
        0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, 0x9af88c27,
        0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63,
        0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, 0x39720a3d,
        0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
        0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234,
        0x92638212, 0x670efa8e, 0x406000e0, 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742,
        0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be,
        0x6a366eb4, 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
        0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8,
        0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1,
        0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, 0x80e4a915, 0x87b08601, 0x9b09e6ad,
        0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
        0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d,
        0x283b57cc, 0xf8d56629, 0x79132e28, 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba,
        0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb,
        0x26dcf319, 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
        0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, 0xa8b6e37e,
        0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd,
        0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, 0xdda26a7e, 0x3a59ff45, 0x3e350a44,
        0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
        0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428,
        0x95983a1d, 0x06b89fb4, 0xce6ea048, 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc,
        0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3,
        0xa1e8aac7, 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
        0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, 0xfae59361,
        0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b,
        0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, 0x1b0a7441, 0x4ba3348c, 0xc5be7120,
        0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
        0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162,
        0x5a75ebb5, 0x6e163697, 0x88d273cc, 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a,
        0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c,
        0xcd769c2b, 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
        0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869,
        0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
    };

    #endregion Initial contents of key schedule

    // BCrypt IV: "OrpheanBeholderScryDoubt"
    private static readonly uint[] BfCryptCiphertext =
        new uint[] { 0x4f727068, 0x65616e42, 0x65686f6c, 0x64657253, 0x63727944, 0x6f756274 };

    private static readonly int BfCryptCiphertextLength = BfCryptCiphertext.Length;

    // Table for Base64 encoding
    private static readonly char[] Base64Code = new[]
    {
        '.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
        'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
        'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
    };

    // Table for Base64 decoding
    private static readonly int[] Index64 = new[]
    {
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 54, 55, 56, 57, 58, 59,
        60, 61, 62, 63, -1, -1, -1, -1, -1, -1, -1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
        20, 21, 22, 23, 24, 25, 26, 27, -1, -1, -1, -1, -1, -1, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
        41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, -1, -1, -1, -1, -1
    };

    // Fixed configuration
    internal const string EmptyString = "";
    internal const char DefaultHashVersion = 'a';
    internal const string Nul = "\0";
    internal const short MinRounds = 4;
    internal const short MaxRounds = 31;

    // Expanded Blowfish key
    private uint[] _p;
    private uint[] _s;

    /// <summary>
    /// 
    /// </summary>
    /// <param name="inputKey"></param>
    /// <param name="salt"></param>
    /// <param name="hashType"></param>
    /// <param name="enhancedHashKeyGen"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException"></exception>
    /// <exception cref="ArgumentException"></exception>
    /// <exception cref="SaltParseException"></exception>
    internal static string CreatePasswordHash(string inputKey, string salt, HashType hashType = HashType.None,
        Func<string, HashType, char, byte[]> enhancedHashKeyGen = null)
    {
        if (inputKey == null)
        {
            throw new ArgumentNullException(nameof(inputKey));
        }

        if (string.IsNullOrEmpty(salt))
        {
            throw new ArgumentException("Invalid salt: salt cannot be null or empty", nameof(salt));
        }

        if (enhancedHashKeyGen == null && hashType != HashType.None)
            throw new ArgumentException(
                "Invalid HashType, You can't have an enhanced hash without an implementation of the key generator.",
                nameof(hashType));

        // Determine the starting offset and validate the salt
        int startingOffset;
        char bcryptMinorRevision = (char)0;
        if (salt[0] != '$' || salt[1] != '2')
        {
            throw new SaltParseException("Invalid salt version");
        }

        if (salt[2] == '$')
        {
            startingOffset = 3;
        }
        else
        {
            bcryptMinorRevision = salt[2];
            if (bcryptMinorRevision != 'a' && bcryptMinorRevision != 'b' && bcryptMinorRevision != 'x' &&
                bcryptMinorRevision != 'y' || salt[3] != '$')
            {
                throw new SaltParseException("Invalid salt revision");
            }

            startingOffset = 4;
        }

        // Extract number of rounds
        if (salt[startingOffset + 2] > '$')
        {
            throw new SaltParseException("Missing salt rounds");
        }

        // Extract details from salt
        int workFactor = Convert.ToInt16(salt.Substring(startingOffset, 2));

        // Throw if log rounds are out of range on hash, deals with custom salts
        if (workFactor < 1 || workFactor > 31)
        {
            throw new SaltParseException("Salt rounds out of range");
        }

        byte[] inputBytes;
        switch (hashType)
        {
            case HashType.None:
                inputBytes = SafeUTF8.GetBytes(inputKey + (bcryptMinorRevision >= 'a' ? Nul : EmptyString));
                break;
            default:
                if(enhancedHashKeyGen == null)
                    throw new ArgumentException("Invalid HashType, You can't have an enhanced hash without an implementation of the key generator.", nameof(hashType));
                inputBytes = enhancedHashKeyGen(inputKey, hashType, bcryptMinorRevision);
                break;
        }

        return HashBytes(inputBytes, salt.Substring(startingOffset + 3, 22), bcryptMinorRevision, workFactor);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="inputBytes"></param>
    /// <param name="extractedSalt"></param>
    /// <param name="bcryptMinorRevision"></param>
    /// <param name="workFactor"></param>
    /// <returns></returns>
    internal static string HashBytes(byte[] inputBytes, string extractedSalt, char bcryptMinorRevision, int workFactor)
    {
        byte[] saltBytes = DecodeBase64(extractedSalt, BCryptSaltLen);

        BCrypt bCrypt = new BCrypt();

        byte[] hashed = bCrypt.CryptRaw(inputBytes, saltBytes, workFactor);

        // Generate result string
        var result = new StringBuilder(60);
        result.Append("$2").Append(bcryptMinorRevision).Append('$').Append(workFactor.ToString("D2")).Append('$');
        result.Append(EncodeBase64(saltBytes, saltBytes.Length));
        result.Append(EncodeBase64(hashed, (BfCryptCiphertextLength * 4) - 1));

        return result.ToString();
    }

    /// <summary>
    ///  Generate a salt for use with the <see cref="BCrypt.HashPassword(string, string)"/> method.
    /// </summary>
    /// <param name="workFactor">The log2 of the number of rounds of hashing to apply - the work
    ///                          factor therefore increases as 2**workFactor.</param>
    /// <param name="bcryptMinorRevision"></param>
    /// <exception cref="ArgumentOutOfRangeException">Work factor must be between 4 and 31</exception>
    /// <returns>A base64 encoded salt value.</returns>
    /// <exception cref="ArgumentException">BCrypt Revision should be a, b, x or y</exception>
    internal static string GenerateSalt(int workFactor = DefaultRounds, char bcryptMinorRevision = DefaultHashVersion)
    {
        if (workFactor < MinRounds || workFactor > MaxRounds)
        {
            throw new ArgumentOutOfRangeException(nameof(workFactor), workFactor,
                $"The work factor must be between {MinRounds} and {MaxRounds} (inclusive)");
        }

        if (bcryptMinorRevision != 'a' && bcryptMinorRevision != 'b' && bcryptMinorRevision != 'x' &&
            bcryptMinorRevision != 'y')
        {
            throw new ArgumentException("BCrypt Revision should be a, b, x or y", nameof(bcryptMinorRevision));
        }

        byte[] saltBytes = new byte[BCryptSaltLen];

        RngCsp.GetBytes(saltBytes);

        var result = new StringBuilder(29);
        result.Append("$2").Append(bcryptMinorRevision).Append('$').Append(workFactor.ToString("D2")).Append('$');
        result.Append(EncodeBase64(saltBytes, saltBytes.Length));

        return result.ToString();
    }

    // Compares two byte arrays for equality. The method is specifically written so that the loop is not optimised.
    [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
    internal static bool SecureEquals(byte[] a, byte[] b)
    {
        if (a == null && b == null)
        {
            return true;
        }

        if (a == null || b == null || a.Length != b.Length)
        {
            return false;
        }

        int diff = 0;
        for (var i = 0; i < a.Length; i++)
        {
            diff |= (a[i] ^ b[i]);
        }

        return diff == 0;
    }


    /// <summary>
    ///  Encode a byte array using BCrypt's slightly-modified base64 encoding scheme. Note that this
    ///  is *not* compatible with the standard MIME-base64 encoding.
    /// </summary>
    /// <exception cref="ArgumentException">Thrown when one or more arguments have unsupported or
    ///                                     illegal values.</exception>
    /// <param name="byteArray">The byte array to encode.</param>
    /// <param name="length">   The number of bytes to encode.</param>
    /// <returns>Base64-encoded string.</returns>
    internal static char[] EncodeBase64(byte[] byteArray, int length)
    {
        if (length <= 0 || length > byteArray.Length)
        {
            throw new ArgumentException("Invalid length", nameof(length));
        }

        int encodedSize = (int)Math.Ceiling((length * 4D) / 3);
        char[] encoded = new char[encodedSize];

        int pos = 0;
        int off = 0;
        while (off < length)
        {
            //Process first byte in group
            int c1 = byteArray[off++] & 0xff;
            encoded[pos++] = Base64Code[(c1 >> 2) & 0x3f];
            c1 = (c1 & 0x03) << 4;
            if (off >= length)
            {
                encoded[pos++] = Base64Code[c1 & 0x3f];
                break;
            }

            // second byte of group
            int c2 = byteArray[off++] & 0xff;
            c1 |= (c2 >> 4) & 0x0f;
            encoded[pos++] = Base64Code[c1 & 0x3f];
            c1 = (c2 & 0x0f) << 2;
            if (off >= length)
            {
                encoded[pos++] = Base64Code[c1 & 0x3f];
                break;
            }

            // third byte of group
            c2 = byteArray[off++] & 0xff;
            c1 |= (c2 >> 6) & 0x03;
            encoded[pos++] = Base64Code[c1 & 0x3f];
            encoded[pos++] = Base64Code[c2 & 0x3f];
        }

        return encoded;
    }


    /// <summary>
    ///  Decode a string encoded using BCrypt's base64 scheme to a byte array.
    ///  Note that this is *not* compatible with the standard MIME-base64 encoding.
    /// </summary>
    /// <exception cref="ArgumentException">Thrown when one or more arguments have unsupported or
    ///                                     illegal values.</exception>
    /// <param name="encodedString">The string to decode.</param>
    /// <param name="maximumBytes"> The maximum bytes to decode.</param>
    /// <returns>The decoded byte array.</returns>
    internal static byte[] DecodeBase64(string encodedString, int maximumBytes)
    {
        int sourceLength = encodedString.Length;
        int outputLength = 0;

        if (maximumBytes <= 0)
        {
            throw new ArgumentException("Invalid maximum bytes value", nameof(maximumBytes));
        }

        byte[] result = new byte[maximumBytes];

        int position = 0;
        while (position < sourceLength - 1 && outputLength < maximumBytes)
        {
            int c1 = Char64(encodedString[position++]);
            int c2 = Char64(encodedString[position++]);
            if (c1 == -1 || c2 == -1)
            {
                break;
            }

            result[outputLength] = (byte)((c1 << 2) | ((c2 & 0x30) >> 4));
            if (++outputLength >= maximumBytes || position >= sourceLength)
            {
                break;
            }

            int c3 = Char64(encodedString[position++]);
            if (c3 == -1)
            {
                break;
            }

            result[outputLength] = (byte)(((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2));
            if (++outputLength >= maximumBytes || position >= sourceLength)
            {
                break;
            }

            int c4 = Char64(encodedString[position++]);
            result[outputLength] = (byte)(((c3 & 0x03) << 6) | c4);

            ++outputLength;
        }

        return result;
    }

    /// <summary>
    ///  Look up the 3 bits base64-encoded by the specified character, range-checking against
    ///  conversion table.
    /// </summary>
    /// <param name="character">The base64-encoded value.</param>
    /// <returns>The decoded value of x.</returns>
    private static int Char64(char character)
    {
        return character < 0 || character > Index64.Length ? -1 : Index64[character];
    }

    /// <summary>Blowfish encipher a single 64-bit block encoded as two 32-bit halves.</summary>
    /// <param name="blockArray">An array containing the two 32-bit half blocks. The plaintext to be encrypted</param>
    /// <param name="offset">    The position in the array of the blocks.</param>
#if NETCOREAPP
    private void Encipher(Span<uint> blockArray, int offset)
#else
    private void Encipher(uint[] blockArray, int offset)
#endif
    {
        uint block = blockArray[offset];
        uint r = blockArray[offset + 1];

        block ^= _p[0];

        unchecked
        {
            uint round;
            for (round = 0; round <= BlowfishNumRounds - 2;)
            {
                // Feistel substitution on left word
                uint n = _s[(block >> 24) & 0xff];
                n += _s[0x100 | ((block >> 16) & 0xff)];
                n ^= _s[0x200 | ((block >> 8) & 0xff)];
                n += _s[0x300 | (block & 0xff)];
                r ^= n ^ _p[++round];

                // Feistel substitution on right word
                n = _s[(r >> 24) & 0xff];
                n += _s[0x100 | ((r >> 16) & 0xff)];
                n ^= _s[0x200 | ((r >> 8) & 0xff)];
                n += _s[0x300 | (r & 0xff)];
                block ^= n ^ _p[++round];
            }

            blockArray[offset] = r ^ _p[BlowfishNumRounds + 1];
            blockArray[offset + 1] = block;
        }
    }

    /// <summary>Cyclically extract a word of key material.</summary>
    /// <param name="data">The string to extract the data from.</param>
    /// <param name="offset"> [in, out] The current offset.</param>
    /// <returns>The next word of material from data.</returns>
#if NETCOREAPP
    private static uint StreamToWord(ReadOnlySpan<byte> data, ref int offset)
#else
    private static uint StreamToWord(byte[] data, ref int offset)
#endif
    {
        int i;
        uint word = 0;

        for (i = 0; i < 4; i++)
        {
            word = (word << 8) | (uint)(data[offset] & 0xff);
            offset = (offset + 1) % data.Length;
        }

        return word;
    }

    /// <summary>Initializes the Blowfish key schedule.</summary>
    private void InitializeKey()
    {
        _p = new uint[POrig.Length];
        _s = new uint[SOrig.Length];
        Array.Copy(POrig, _p, POrig.Length);
        Array.Copy(SOrig, _s, SOrig.Length);
    }

    /// <summary>Key the Blowfish cipher.</summary>
    /// <param name="keyBytes">The key byte array.</param>
#if NETCOREAPP
    private void Key(ReadOnlySpan<byte> keyBytes)
#else
    private void Key(byte[] keyBytes)
#endif
    {
        int i;
        int kOfP = 0;
#if NETCOREAPP
        Span<uint> lr = stackalloc uint[2] { 0, 0 };
#else
        uint[] lr = { 0, 0 };
#endif
        int pLen = _p.Length, sLen = _s.Length;

        for (i = 0; i < pLen; i++)
        {
            _p[i] = _p[i] ^ StreamToWord(keyBytes, ref kOfP);
        }

        for (i = 0; i < pLen; i += 2)
        {
            Encipher(lr, 0);
            _p[i] = lr[0];
            _p[i + 1] = lr[1];
        }

        for (i = 0; i < sLen; i += 2)
        {
            Encipher(lr, 0);
            _s[i] = lr[0];
            _s[i + 1] = lr[1];
        }
    }

    /// <summary>
    ///  Perform the "enhanced key schedule" step described by Provos and Mazieres in
    ///  "A Future Adaptable Password Scheme" http://www.openbsd.org/papers/bcrypt-paper.ps.
    /// </summary>
    /// <param name="saltBytes"> Salt byte array.</param>
    /// <param name="inputBytes">Input byte array.</param>
    // ReSharper disable once InconsistentNaming
#if NETCOREAPP
    private void EKSKey(ReadOnlySpan<byte> saltBytes, ReadOnlySpan<byte> inputBytes)
#else
    private void EKSKey(byte[] saltBytes, byte[] inputBytes)
#endif
    {
        int i;
        int passwordOffset = 0;
        int saltOffset = 0;
#if NETCOREAPP
        Span<uint> lr = stackalloc uint[2] { 0, 0 };
#else
        uint[] lr = { 0, 0 };
#endif
        int pLen = _p.Length, sLen = _s.Length;

        for (i = 0; i < pLen; i++)
        {
            _p[i] = _p[i] ^ StreamToWord(inputBytes, ref passwordOffset);
        }

        for (i = 0; i < pLen; i += 2)
        {
            lr[0] ^= StreamToWord(saltBytes, ref saltOffset);
            lr[1] ^= StreamToWord(saltBytes, ref saltOffset);
            Encipher(lr, 0);
            _p[i] = lr[0];
            _p[i + 1] = lr[1];
        }

        for (i = 0; i < sLen; i += 2)
        {
            lr[0] ^= StreamToWord(saltBytes, ref saltOffset);
            lr[1] ^= StreamToWord(saltBytes, ref saltOffset);
            Encipher(lr, 0);
            _s[i] = lr[0];
            _s[i + 1] = lr[1];
        }
    }

    /// <summary>Perform the central hashing step in the BCrypt scheme.</summary>
    /// <exception cref="ArgumentException">Thrown when one or more arguments have unsupported or
    ///                                     illegal values.</exception>
    /// <param name="inputBytes">The input byte array to hash.</param>
    /// <param name="saltBytes"> The salt byte array to hash with.</param>
    /// <param name="workFactor"> The binary logarithm of the number of rounds of hashing to apply.</param>
    /// <returns>A byte array containing the hashed result.</returns>
#if NETCOREAPP
    internal byte[] CryptRaw(ReadOnlySpan<byte> inputBytes, ReadOnlySpan<byte> saltBytes, int workFactor)
#else
    internal byte[] CryptRaw(byte[] inputBytes, byte[] saltBytes, int workFactor)
#endif
    {
        int i;
        int j;

#if NETCOREAPP
        Span<uint> cdata = stackalloc uint[BfCryptCiphertext.Length];
        BfCryptCiphertext.CopyTo(cdata);
#else
        uint[] cdata = new uint[BfCryptCiphertext.Length];
        Array.Copy(BfCryptCiphertext, cdata, BfCryptCiphertext.Length);
#endif

        int clen = cdata.Length;

        if (workFactor < MinRounds || workFactor > MaxRounds)
        {
            throw new ArgumentException("Bad number of rounds", nameof(workFactor));
        }

        if (saltBytes.Length != BCryptSaltLen)
        {
            throw new ArgumentException("Bad salt Length", nameof(saltBytes));
        }

        uint rounds = 1u << workFactor;

        // We overflowed rounds at 31 - added safety check
        if (rounds < 1)
        {
            throw new ArgumentException("Bad number of rounds", nameof(workFactor));
        }

        InitializeKey();
        EKSKey(saltBytes, inputBytes);

        for (i = 0; i != rounds; i++)
        {
            Key(inputBytes);
            Key(saltBytes);
        }

        for (i = 0; i < 64; i++)
        {
            for (j = 0; j < (clen >> 1); j++)
            {
                Encipher(cdata, j << 1);
            }
        }

        // Convert ciphertext to output byte-array
        byte[] ret = new byte[clen * 4];
        for (i = 0, j = 0; i < clen; i++)
        {
            // per-line extract first byte by shifting cdata word at index right 24 bits
            // using >> op then isolate the least significant byte using mask 0xff
            ret[j++] = (byte)((cdata[i] >> 24) & 0xff);
            ret[j++] = (byte)((cdata[i] >> 16) & 0xff);
            ret[j++] = (byte)((cdata[i] >> 8) & 0xff);
            ret[j++] = (byte)(cdata[i] & 0xff);
        }

        return ret;
    }
}
